//modified by thesean to avoid libnds collision - sept 2008
//minor bug fixes made with liranuna.

#include "ellen/sprites.h"
#include <cstdlib>
#include <string.h>
#include <nds.h>
#include <stdio.h>
		
// ----------------- Data		-----------------
		
lntOAM oamBuffer;
bool sprActive[LN_SCREEN_COUNT][LN_SPR_COUNT];
lnSpriteAnim sprAnimBuf[LN_SCREEN_COUNT][LN_SPR_COUNT];
		
	// LUT for sprite size
const lntSprSize sprSizeLUT[4][4] = {
	{ SPRSIZE_8x8,		SPRSIZE_8x16,		SPRSIZE_8x32,	SPRSIZE_INVALID,	},
	{ SPRSIZE_16x8, 	SPRSIZE_16x16,		SPRSIZE_16x32,	SPRSIZE_INVALID,	},
	{ SPRSIZE_32x8,		SPRSIZE_32x16,		SPRSIZE_32x32,	SPRSIZE_32x64,		},
	{ SPRSIZE_INVALID,	SPRSIZE_INVALID,	SPRSIZE_64x32,	SPRSIZE_64x64,		},
};
		
// ----------------- Functions	-----------------
		
void lnSpritesInit()
{
	for(int curScreen=0; curScreen<LN_SCREEN_COUNT; ++curScreen) {
		for(int curSprite=0; curSprite<LN_SPR_COUNT; ++curSprite) {
			lnSprite* curSpriteData = &oamBuffer.sprBuffer[curScreen][curSprite];
				
				// Zeroing data
			memset(curSpriteData, 0, sizeof(lnSprite));
				// Hide sprite
			curSpriteData->isHidden = true;
				
				// All sprites are unused
			sprActive[curScreen][curSprite] = false;
				
				// All animations are unused
			sprAnimBuf[curScreen][curSprite].animType = ANIMTYPE_OFF;
		}
			
		for(int curMatrix=0; curMatrix<LN_MTX_COUNT; ++curMatrix) {
			lnSprRot* curSprRotData = &oamBuffer.sprRotBuf[curScreen][curMatrix];
				
				// 8.8 signed fixed
			curSprRotData->XDX = 1 << 8;
			curSprRotData->XDY = 0 << 8;
			curSprRotData->YDX = 0 << 8;
			curSprRotData->YDY = 1 << 8;
		}
	}
		
	lnSpritesUpdate();
		
		// Setting VRAM banks
		// FIXME: hardcoded VRAM banks!
		// SES -  will it be a problem sharing I between sub_sprite and sub_sprite_palette?
		//			should be okay if I make sure not to overwrite one another...

		//SES NOTE:
		//when reassigning the hardcoding of these vram banks, make sure the desired bank is defined
		//for the proper usage.  Check GBATek to make sure.  (ie, VRAM_D can't be mapped to MAIN_SPRITE)
		//grep for FIXME to see all hardcoded vram bank spots in code.
		
		// Sprite data
	VRAM_E_CR = VRAM_ENABLE | VRAM_E_MAIN_SPRITE;
	VRAM_I_CR = VRAM_ENABLE | VRAM_I_SUB_SPRITE;
		// Extended palettes
	VRAM_G_CR = VRAM_ENABLE | VRAM_G_OBJ_EXT_PALETTE;
	//VRAM_I_CR = VRAM_ENABLE | VRAM_I_SUB_SPRITE_EXT_PALETTE;
		
		// Now enabling sprites
	DISPLAY_CR		|= DISPLAY_SPR_ACTIVE | DISPLAY_SPR_1D | DISPLAY_SPR_EXT_PALETTE;
	SUB_DISPLAY_CR	|= DISPLAY_SPR_ACTIVE | DISPLAY_SPR_1D | DISPLAY_SPR_EXT_PALETTE;
}

void lnSpritesUpdate()
{
		// Copying OAM buffer
	memcpy(OAM, &oamBuffer, sizeof(lntOAM));
		
		// Handling all sprites animations
	for(int s=0; s<LN_SCREEN_COUNT; ++s) {
		for(int i=0; i<LN_SPR_COUNT; ++i)
			lnSpriteAnimHandle(&sprAnimBuf[s][i]);
	}
}

lnSprite* lnSpriteByIndex(lntScreen cScreen, int sprIdx)
{
		// Error checking
	if(sprIdx < 0)
		return NULL;
	if(sprIdx > LN_SPR_COUNT - 1)
		return NULL;
	if(cScreen == LN_SCREEN_COUNT)
		return NULL;
		
		// Uhm... yeah...
	return &oamBuffer.sprBuffer[cScreen][sprIdx];
}

lnSprite* lnSpriteNew(lntScreen cScreen)
{
	if(cScreen == LN_SCREEN_COUNT)
		return NULL;
		
	lnSprite* curPack = oamBuffer.sprBuffer[cScreen];
		
	int curSprite = 0;
	while(sprActive[cScreen][curSprite]) {
			// Reached sprite capacity
		if(curSprite > LN_SPR_COUNT - 1)
			return NULL;
			
		++curSprite;
	}
		
		// Reset sprite and avoiding attribute3 (rotation matrix)
	memset(&curPack[curSprite], 0, sizeof(lnSprite) - sizeof(u16));
		// Mark as used
	sprActive[cScreen][curSprite] = true;
		
	return lnSpriteByIndex(cScreen, curSprite);
}

void lnSpriteDestroy(lnSprite* cSprite)
{
	const int sprIndex = lnSpriteIndex(cSprite);
	const lntScreen sprScreen = lnSpriteScreen(cSprite);
		
		// Errors
	if(sprScreen == LN_SCREEN_COUNT)
		return;
	if(sprIndex < 0)
		return;
		
		// Mark as unused
	sprActive[sprScreen][sprIndex] = false;
		
		// Hide sprite
	cSprite->isHidden = true;
		
		// When a sprite is in a rotoscale mode,
		// isHidden is actually 'rsDouble' so we
		// make sure we disable it too
	cSprite->isRotoscale = false;

	//AKS modified
	sprAnimBuf[sprScreen][sprIndex].animType = ANIMTYPE_OFF;

}

lnSprite* lnSpriteCreate(lntScreen cScreen, lntSprSize sprSize, lntColMode colMode, const void* sprData, const void* sprPalette)
{
	if(sprSize == SPRSIZE_INVALID)
		return NULL;
		
	lnSprite* retSpr = lnSpriteNew(cScreen);
		
		// No more sprites
	if(retSpr == NULL)
		return NULL;
		
		// Set new size
	lnSpriteResize(retSpr, sprSize);
		// Set and copy tiles
	lnSpriteSetTiles(retSpr, sprData, colMode);
		// Set and copy palette
	lnSpriteSetPalette(retSpr, sprPalette, colMode);
		
	return retSpr;
}

/*
					lo.ind > hi.ind		lo.ind < hi.ind
lo.prio > hi.prio	LOW						LOW
lo.prio = hi.prio	HIGH					LOW
lo.prio < hi.prio	HIGH					HIGH
*/

void lnSpriteSetAbove(lnSprite* hiSprite, lnSprite* loSprite)
{
		// 0 is highest priority, so operator is reversed
	if(hiSprite->objPriority < loSprite->objPriority) { 
		
	}
		
		// Sprites are already above index wise
	if(lnSpriteIndex(hiSprite) < lnSpriteIndex(loSprite))
		return;
}

lnSprite* lnSpriteDuplicate(lnSprite* srcSprite, bool copyGfx)
{
	lntScreen sprScreen = lnSpriteScreen(srcSprite);
		
	if(sprScreen == LN_SCREEN_COUNT)
		return NULL;
		
		// Allocate a new sprite
	lnSprite* newSprite = lnSpriteNew(sprScreen);
		
		// No more sprites
	if(newSprite == NULL)
		return NULL;
		
		// Copy the sprite and avoiding attribute 3 (rotoscale)
	memcpy(newSprite, srcSprite, sizeof(lnSprite) - sizeof(u16));
		
		// Duplicate graphics
	if(copyGfx) {
		lnSpriteSetTiles(newSprite, lnSpriteGetTiles(srcSprite), srcSprite->colMode);
			
			// FIXME: hardcoded!
			// Unlocking both VRAM banks
		VRAM_G_CR = VRAM_ENABLE | VRAM_G_LCD;
		//VRAM_I_CR = VRAM_ENABLE | VRAM_I_LCD;
			
		lnSpriteSetPalette(newSprite, lnSpriteGetPalette(srcSprite), srcSprite->colMode);
			
			// FIXME: hardcoded!
			// Re-locking both VRAM banks
		VRAM_G_CR = VRAM_ENABLE | VRAM_G_OBJ_EXT_PALETTE;
		//VRAM_I_CR = VRAM_ENABLE | VRAM_I_SUB_SPRITE_EXT_PALETTE;
			
			// Copying animation is only possible if graphics are duplicated
		lnSpriteAnim* newSpriteAnim = lnSpriteAnimGet(newSprite);
		*newSpriteAnim = *lnSpriteAnimGet(srcSprite);
		newSpriteAnim->animSprite = newSprite;
	}
		
		// Presto!
	return newSprite;
}

lnSprite* lnSpriteScreenDuplicate(lnSprite* srcSprite)
{
	lntScreen sprScreen = lnSpriteScreen(srcSprite);
		
	if(sprScreen == LN_SCREEN_COUNT)
		return NULL;
		
		// FIXME: hardcoded!
		// Unlocking both VRAM banks
	VRAM_G_CR = VRAM_ENABLE | VRAM_G_LCD;
	//VRAM_I_CR = VRAM_ENABLE | VRAM_I_LCD;
		
		// Trick to select the opposite screen faster
	lnSprite* newSprite = lnSpriteNew((lntScreen)(sprScreen ^ 1));
		// Copy the sprite and avoiding attribute 3 (rotoscale)
	memcpy(newSprite, srcSprite, sizeof(lnSprite) - sizeof(u16));
		
		// Set new size
	lnSpriteResize(newSprite, lnSpriteSize(srcSprite));
		// Set and copy tiles
	lnSpriteSetTiles(newSprite, lnSpriteGetTiles(srcSprite), srcSprite->colMode);
		// Set and copy palette
	lnSpriteSetPalette(newSprite, lnSpriteGetPalette(srcSprite), srcSprite->colMode);	
		// Copy Animation
	lnSpriteAnim* newSpriteAnim = lnSpriteAnimGet(newSprite);
	*newSpriteAnim = *lnSpriteAnimGet(srcSprite);
	newSpriteAnim->animSprite = newSprite;
		
		// FIXME: hardcoded!
		// Re-locking both VRAM banks
	VRAM_G_CR = VRAM_ENABLE | VRAM_G_OBJ_EXT_PALETTE;
	//VRAM_I_CR = VRAM_ENABLE | VRAM_I_SUB_SPRITE_EXT_PALETTE;
		
	return newSprite;
}

void lnSpriteResize(lnSprite* cSprite, lntSprSize sprSize)
{
	if(sprSize == SPRSIZE_INVALID)
		return;
		
	cSprite->objShape = (lntObjShape)(sprSize % lnOBJSHAPE_FORBIDDEN);
	cSprite->objSize  = (lntObjSize )(sprSize / lnOBJSHAPE_FORBIDDEN);
}

void lnSpriteEnableRotation(lnSprite* cSprite, bool rsDouble, bool autoAlloc)
{
		// Set rotation flag on
	cSprite->isRotoscale = true;
		// Set double size flag if needed
	cSprite->rsDouble = rsDouble;
		// Allocate new rotation matrix if requested
	if(autoAlloc)
		cSprite->rsMatrixIdx = lnSpriteAllocMatrix(cSprite);
}

void lnSpriteRotate(lnSprite* cSprite, int iAngle)
{
	if(!cSprite->isRotoscale)
		lnSpriteEnableRotation(cSprite);
		
	lnSprRot* sprMatrix = &oamBuffer.sprRotBuf[lnSpriteScreen(cSprite)][cSprite->rsMatrixIdx];
		
	s16 s = SIN[iAngle & 0x1FF] >> 4;
	s16 c = COS[iAngle & 0x1FF] >> 4;
		
	sprMatrix->XDX =  c;
	sprMatrix->XDY =  s;
	sprMatrix->YDX = -s;
	sprMatrix->YDY =  c;
}

lntScreen lnSpriteScreen(lnSprite* cSprite)
{
	if(cSprite < oamBuffer.sprBuffer[SCREEN_MAIN])
		return LN_SCREEN_COUNT; // Invalid
		
	if(cSprite < oamBuffer.sprBuffer[SCREEN_SUB])
		return SCREEN_MAIN;
		
	if(cSprite < oamBuffer.sprBuffer[LN_SCREEN_COUNT])
		return SCREEN_SUB;
		
	return LN_SCREEN_COUNT; // Invalid
}

int lnSpriteIndex(lnSprite* cSprite)
{
	const lntScreen scrSprite = lnSpriteScreen(cSprite);
		
		// Error
	if(scrSprite == LN_SCREEN_COUNT)
		return -1;
		
	return cSprite - oamBuffer.sprBuffer[scrSprite];
}

lntSprSize lnSpriteSize(lnSprite* cSprite)
{
		// Guh, one line functions sucks
		// So does casting, but there's no other way of doing that (without wasting useful RAM)
	return (lntSprSize)((cSprite->objSize * lnOBJSHAPE_FORBIDDEN) + cSprite->objShape);
}

lntSprSize lnSpritePixelSize(u8 dimX, u8 dimY)
{
		// Not a power of 2 width
	if(dimX & (dimX - 1))
		return SPRSIZE_INVALID;
		
		// Not a power of 2 height
	if(dimY & (dimY - 1))
		return SPRSIZE_INVALID;
		
		// Width is more then HW sprite size
	if(dimX > 64 || dimX < 8)
		return SPRSIZE_INVALID;
		// Height is more then HW sprite size
	if(dimY > 64 || dimY < 8)
		return SPRSIZE_INVALID;
		
		// Find the log base 2 of those two
	dimX = ((dimX & 0xAA) != 0) | ((dimX & 0xCC) != 0) << 1 | ((dimX & 0xF0) != 0) << 2;
	dimY = ((dimY & 0xAA) != 0) | ((dimY & 0xCC) != 0) << 1 | ((dimY & 0xF0) != 0) << 2;
		
		// LUT away
	return sprSizeLUT[dimX - 3][dimY - 3];
}

u8 lnSpriteWidth(lnSprite* cSprite)
{
	switch(lnSpriteSize(cSprite)) {
		case SPRSIZE_8x8:
		case SPRSIZE_8x16:
		case SPRSIZE_8x32:
			return 8;
		case SPRSIZE_16x8:
		case SPRSIZE_16x16:
		case SPRSIZE_16x32:
			return 16;
		case SPRSIZE_32x8:
		case SPRSIZE_32x16:
		case SPRSIZE_32x32:
		case SPRSIZE_32x64:
			return 32;
		case SPRSIZE_64x32:
		case SPRSIZE_64x64:
			return 64;
		case SPRSIZE_INVALID:
			return 0;
	}
		
		// To keep compilers happy
	return 0;
}

u8 lnSpriteHeight(lnSprite* cSprite)
{
	switch(lnSpriteSize(cSprite)) {
		case SPRSIZE_8x8:
		case SPRSIZE_16x8:
		case SPRSIZE_32x8:
			return 8;
		case SPRSIZE_8x16:
		case SPRSIZE_16x16:
		case SPRSIZE_32x16:
			return 16;
		case SPRSIZE_8x32:
		case SPRSIZE_16x32:
		case SPRSIZE_32x32:
		case SPRSIZE_64x32:
			return 32;
		case SPRSIZE_32x64:
		case SPRSIZE_64x64:
			return 64;
		case SPRSIZE_INVALID:
			return 0;
	}
		
		// To keep compilers happy
	return 0;
}

int lnSpriteTileCount(lnSprite* cSprite)
{
		// (NxM)/(HxW)
	int tileCount = (lnSpriteHeight(cSprite) / HW_TILE_HEIGHT) * (lnSpriteWidth(cSprite) / HW_TILE_WIDTH);
		
		// Double the count when color mode is 256col
	return tileCount;
}

bool lnSpriteCollision(lnSprite* cSprite1, lnSprite* cSprite2)
{
	int startX1 = cSprite1->posX;
	int startX2 = cSprite2->posX;
	int endX1 = startX1 + lnSpriteWidth(cSprite1);
	int endX2 = startX2 + lnSpriteWidth(cSprite2);
		
	if(startX1 > endX2)
		return false;
		
	if(endX1 < startX2)
		return false;
		
	int startY1 = cSprite1->posY;
	int startY2 = cSprite2->posY;
	int endY1 = startY1 + lnSpriteHeight(cSprite1);
	int endY2 = startY2 + lnSpriteHeight(cSprite2);
		
	if(startY1 > endY2)
		return false;
		
	if(endY1 < startY2)
		return false;
		
	return true;
}

u16* const lnSpriteGetTiles(lnSprite* cSprite)
{
	const lntScreen sprScreen = lnSpriteScreen(cSprite);
		
		// Yet again, one line functions suck, but the wrapper is needed
	return LN_SPR_TILEADDR(sprScreen, cSprite->tileIdx);
}

u16* const lnSpriteGetPalette(lnSprite* cSprite)
{
	lntScreen sprScreen = lnSpriteScreen(cSprite);
		
	u16* palLocation = SPRITE_PALETTE;
		
	if(cSprite->colMode == COLMODE_16)
		palLocation += (SPRITE_PALETTE_SUB - SPRITE_PALETTE) * sprScreen;
	else if(cSprite->colMode == COLMODE_256)
		palLocation = (sprScreen == SCREEN_MAIN) ? (OBJ_EXTPAL_MAIN) : (OBJ_EXTPAL_SUB);
		
	return palLocation + ((cSprite->objPal * SIZEOF_PAL_COLMODE(cSprite->colMode)) / PALETTE_BPP);
}

int lnSpriteAllocTiles(lnSprite* cSprite)
{
	const lntScreen sprScreen = lnSpriteScreen(cSprite);
		
	bool tilesInUsage[SPR_TILES_COUNT];
	memset(tilesInUsage, false, SPR_TILES_COUNT);
		
		// Marking up used tiles
	for(int s=0; s<LN_SPR_COUNT; ++s) {
		lnSprite* curSprite = lnSpriteByIndex(sprScreen, s);
			
		if(!sprActive[sprScreen][s])
			continue;
			
		if(curSprite == cSprite)
			continue;
			
		for(int i=0; i<lnSpriteTileCount(curSprite) << curSprite->colMode; ++i)
			tilesInUsage[(curSprite->tileIdx << LN_SPR_TILEBOUNDRY) + i] = true;
	}
		
	const int tilesToAlloc = lnSpriteTileCount(cSprite) << cSprite->colMode;
	for(int i=0; i<SPR_TILES_COUNT; i+=LN_SPR_TILEBOUNDRY + 1) {
		int s = 0;
		for(int t=i; t<i+tilesToAlloc; ++t)
			s += tilesInUsage[t];
		if(s == 0)
			return i >> LN_SPR_TILEBOUNDRY;
	}
		
	return -1;
}

int lnSpriteAllocPallete(lnSprite* cSprite)
{
	lntScreen sprScreen = lnSpriteScreen(cSprite);
		
	bool palInUse[LN_PAL_COUNT];
	memset(palInUse, false, sizeof(palInUse));
		
		// Marking up used palettes
	for(int s=0; s<LN_SPR_COUNT; ++s) {
		lnSprite* curSprite = lnSpriteByIndex(sprScreen, s);
			
		if(!sprActive[sprScreen][s])
			continue;
			
		if(curSprite == cSprite)
			continue;
			
		palInUse[curSprite->objPal] |= (curSprite->colMode == cSprite->colMode);
	}
		
		// Find an unused palette
	for(int i=0; i<LN_PAL_COUNT; ++i) {
		if(!palInUse[i])
			return i;
	}
		
	return -1;
}

int lnSpriteAllocMatrix(lnSprite* cSprite)
{
	lntScreen sprScreen = lnSpriteScreen(cSprite);
		
	bool matrixInUse[LN_MTX_COUNT];
	memset(matrixInUse, false, sizeof(matrixInUse));
		
		// Marking up used palettes
	for(int s=0; s<LN_SPR_COUNT; ++s) {
		lnSprite* curSprite = lnSpriteByIndex(sprScreen, s);
			
		if(!sprActive[sprScreen][s])
			continue;
			
		if(curSprite == cSprite)
			continue;
			
		if(!curSprite->isRotoscale)
			continue;
			
		matrixInUse[curSprite->rsMatrixIdx] = true;
	}
		
		// Find an unused matrix
	for(int i=0; i<LN_SPR_COUNT; ++i) {
		if(!matrixInUse[i])
			return i;
	}
		
	return -1;
}

void lnSpriteSetTiles(lnSprite* cSprite, const void* sprTiles, lntColMode colMode, bool autoAlloc)
{
	cSprite->colMode = colMode;
		
		// Auto allocate tiles if needed
	if(autoAlloc)
		cSprite->tileIdx = lnSpriteAllocTiles(cSprite);
		
		// Final copy to VRAM
	memcpy(lnSpriteGetTiles(cSprite), sprTiles, SIZEOF_TILE_COLMODE(cSprite->colMode) * lnSpriteTileCount(cSprite));
}

void lnSpriteSetPalette(lnSprite* cSprite, const void* sprPalette, lntColMode colMode, bool autoAlloc)
{
		// Cannot handle bitmapped sprites (do not have palettes)
	if(cSprite->objMode == lnOBJMODE_BITMAP)
		return;
		
	const lntScreen sprScreen = lnSpriteScreen(cSprite);
		
		// ERROR
	if(sprScreen == LN_SCREEN_COUNT)
		return;
		
	cSprite->colMode = colMode;
		
	if(autoAlloc)
		cSprite->objPal = lnSpriteAllocPallete(cSprite);
		
	/*iprintf("objPal: %d\n", (int)cSprite->objPal);
	for (int p = 0; p < 40; p++) {
		swiWaitForVBlank();
	}*/

		// FIXME: hardcoded!
		// Unlocking VRAM banks for writing
	VRAM_G_CR = VRAM_ENABLE | VRAM_G_LCD;
	VRAM_I_CR = VRAM_ENABLE | VRAM_I_LCD;
		
		// Copy to Palette memory
	memcpy(lnSpriteGetPalette(cSprite), sprPalette, SIZEOF_PAL_COLMODE(colMode));
		
		// FIXME: hardcoded!
		// Locking VRAM banks
	VRAM_G_CR = VRAM_ENABLE | VRAM_G_OBJ_EXT_PALETTE;
	VRAM_I_CR = VRAM_ENABLE | VRAM_I_SUB_SPRITE_EXT_PALETTE;
}

	// Animation routines

lnSpriteAnim* lnSpriteAnimGet(lnSprite* cSprite)
{
		// Error checking
	const lntScreen sprScreen = lnSpriteScreen(cSprite);
	if(sprScreen == LN_SCREEN_COUNT)
		return NULL;
		
	int sprIndex = lnSpriteIndex(cSprite);
	if(sprIndex == -1)
		return NULL;
		
		// ...
	return &sprAnimBuf[sprScreen][sprIndex];
}

void lnSpriteAnimSet(lnSprite* cSprite, lnSpriteAnim sprAnim)
{
		// Error checking
	const lntScreen sprScreen = lnSpriteScreen(cSprite);
	if(sprScreen == LN_SCREEN_COUNT)
		return;
		
	int sprIndex = lnSpriteIndex(cSprite);
	if(sprIndex == -1)
		return;
		
		// Associate sprite
	sprAnim.animSprite = cSprite;
		
		// Copy sprite animation to global handler array
	sprAnimBuf[sprScreen][sprIndex] = sprAnim;
}

lnSpriteAnim* lnSpriteAnimBind(lnSprite* cSprite, const void* animTiles, int frameCount, int animInterval)
{
	lnSpriteAnim* sprAnim = lnSpriteAnimGet(cSprite);
		
	sprAnim->animTick = 0;
	//SES removed
	//sprAnim->animSize = frameCount;
	//SES END
	sprAnim->animFrame = -1; // Done to invoke the handler to set first frame
	sprAnim->animFrameInc = 1;
	sprAnim->animInterval = animInterval;
	sprAnim->animLoopCnt = 0;
	sprAnim->animStart = 0;
	sprAnim->animEnd = frameCount - 1;
	sprAnim->animType = ANIMTYPE_LOOP;
		
	sprAnim->animTiles = animTiles;
	sprAnim->animSprite = cSprite;
		
	sprAnim->animFrameSize = lnSpriteTileCount(sprAnim->animSprite) * SIZEOF_TILE_COLMODE(sprAnim->animSprite->colMode);
#ifdef __GIF_H__
	sprAnim->gifFrameInterval = NULL;
#endif
		
	return sprAnim;
}

void lnSpriteAnimHandle(lnSpriteAnim* sprAnim)
{
		// Error checking
	if(sprAnim == NULL)
		return;
		
		// No need to handle a disabled animation
	if(sprAnim->animType == ANIMTYPE_OFF)
		return;
		
#ifdef __GIF_H__
	bool isGif = (sprAnim->gifFrameInterval != NULL);
#endif
		
		// Remove 16 ticks (16ms) when in GIF mode, or 1 in frame mode
	sprAnim->animTick -=
#ifdef __GIF_H__
		(isGif << 4) + !isGif;
#else
		1;
#endif
		
		// Tick finished, now change a frame
	if(sprAnim->animTick <= 0) {
			
		sprAnim->animFrame += sprAnim->animFrameInc;
			
			// Get ticks from gif handler if needed
#ifdef __GIF_H__
		if(isGif)
			sprAnim->animTick = sprAnim->gifFrameInterval[sprAnim->animFrame % animSize];
		else
#endif
			sprAnim->animTick = sprAnim->animInterval;
			
			// Cycle finished
		if(sprAnim->animFrame >= sprAnim->animEnd || sprAnim->animFrame <= sprAnim->animStart) {
			int animSize = (sprAnim->animEnd - sprAnim->animStart) + 1;
			switch(sprAnim->animType) {
				case ANIMTYPE_OFF:
						// Nothing
					break;
				case ANIMTYPE_SINGLE:
							// Animation goes to sleep
						sprAnim->animType = ANIMTYPE_OFF;
							// Making sure to stay on last frame
						sprAnim->animFrame -= sprAnim->animFrameInc;
					break;
				case ANIMTYPE_LOOP:
							// Start over untill the loop handler will decide it's over
						if(sprAnim->animLoopCnt != 1)
							sprAnim->animFrame = (sprAnim->animFrame % animSize) + sprAnim->animStart;
						else
							sprAnim->animFrame = sprAnim->animEnd - sprAnim->animFrameInc;
					break;
				case ANIMTYPE_BOUNCE:
							// Step back twice, except on last frame
						sprAnim->animFrame -= sprAnim->animFrameInc << (sprAnim->animLoopCnt != 1);
							// Change animation direction
						sprAnim->animFrameInc = -sprAnim->animFrameInc;
					break;
				case ANIMTYPE_RANDOM:
						sprAnim->animFrame = (rand() % animSize) + sprAnim->animStart;
					break;
			}
				// Special cases - single looping (or loop end) and unlimited looping
			if(sprAnim->animType != ANIMTYPE_SINGLE) {
				if(sprAnim->animLoopCnt == 1)
					sprAnim->animType = ANIMTYPE_OFF;
				else if(sprAnim->animLoopCnt != 0)
					sprAnim->animLoopCnt -= 1;
			}
		}
			
			// Copying current frame data to VRAM
		const u8* curAnimFrame = (u8*)sprAnim->animTiles + (sprAnim->animFrameSize * sprAnim->animFrame);
		lnSpriteSetTiles(sprAnim->animSprite, curAnimFrame, sprAnim->animSprite->colMode, false);
	}
}

void lnSpriteAnimLoop(lnSprite* cSprite, lntAnimType animType, int loopCycles, int incInterval)
{
	lnSpriteAnim* sprAnim = lnSpriteAnimGet(cSprite);
		
		// Error checking
	if(sprAnim == NULL)
		return;
		
		// Set animation type
	sprAnim->animType = animType;
		// Set loop cylce count
	sprAnim->animLoopCnt = loopCycles;
		// Set animation increase interval
	sprAnim->animFrameInc = incInterval;

	//sprAnim->animFrame = sprAnim->animStart;
}

	// GIF routines
#ifdef __GIF_H__

lnSprite* lnSpriteFromGIF(lntScreen cScreen, pGifHandler gifFileHandler)
{
	if(gifFileHandler == NULL)
		return NULL;
		
	lnSprite* newSprite = lnSpriteNew(cScreen);
		
	if(newSprite == NULL)
		return NULL;
		
	lnSpriteSetGIF(newSprite, gifFileHandler);
		
		// Return the new sprite
	return newSprite;
}

void lnSpriteSetGIF(lnSprite* cSprite, pGifHandler gifFileHandler)
{
	if(gifFileHandler == NULL)
		return;
		
		// Convert bitmaps to tiles if needed
	if(gifFileHandler->dataMode != GIFMODE_TILES) {
		if(!gifConvertTiles(gifFileHandler))
			return;
	}
		
		// Translate the GIF dimensions to libellen's internal format
	lntSprSize newSprSize = lnSpritePixelSize(gifFileHandler->dimX, gifFileHandler->dimY);
		
		// Make sure GIF has a valid sprite size
	if(newSprSize == SPRSIZE_INVALID)
		return;
		
	lnSpriteResize(cSprite, newSprSize);
	lnSpriteSetTiles(cSprite, gifFileHandler->gifFrames, COLMODE_256);
	lnSpriteSetPalette(cSprite, gifFileHandler->gifPalette, COLMODE_256);
		
	if(gifFileHandler->frmCount > 1)
		lnSpriteAnimSet(cSprite, lnSpriteAnimFromGIF(gifFileHandler));
}

lnSpriteAnim lnSpriteAnimFromGIF(pGifHandler gifFileHandler)
{
	lnSpriteAnim sprAnim;
		
	sprAnim.animTick = 0;
	sprAnim.animFrame = -1; // Done to invoke the handler to set first frame
	sprAnim.animFrameInc = 1;
	sprAnim.animInterval = 0; // Ununsed, GIF has a time table
	sprAnim.animStart = 0;
		
	sprAnim.animSize = gifFileHandler->frmCount;
	sprAnim.animEnd = gifFileHandler->frmCount;
	sprAnim.animType = ANIMTYPE_LOOP;
	sprAnim.animLoopCnt = gifFileHandler->loopCnt;
		
	sprAnim.animTiles = gifFileHandler->gifFrames;
	sprAnim.animSprite = NULL;
		
	sprAnim.animFrameSize = ((gifFileHandler->dimX / HW_TILE_WIDTH) * (gifFileHandler->dimY / HW_TILE_HEIGHT)) * SIZEOF_TILE_256;
	sprAnim.gifFrameInterval = gifFileHandler->gifTiming;
		
	return sprAnim;
}

#endif
